
<!-- saved from url=(0056)https://betterexplained.com/examples/fourier/?cycles=0,2 -->
<html style="" class=" js flexbox canvas canvastext webgl no-touch geolocation postmessage websqldatabase indexeddb hashchange history draganddrop websockets rgba hsla multiplebgs backgroundsize borderimage borderradius boxshadow textshadow opacity cssanimations csscolumns cssgradients cssreflections csstransforms csstransforms3d csstransitions fontface generatedcontent video audio localstorage sessionstorage webworkers no-applicationcache svg inlinesvg smil svgclippaths"><head><meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<script src="./betterexplained.com_files/underscore-min.js.下载"></script>
<script src="./betterexplained.com_files/jquery.min.js.下载"></script>
<script src="./betterexplained.com_files/modernizr.min.js.下载"></script>
<script src="./betterexplained.com_files/json2.js.下载"></script>

<script type="text/javascript">

	// from view-source:http://treeblurb.com/dev_math/sin_canv00.html
	var x_size = 150;
	var y_size = 100;

    var settings = {

        canvas: {
            width: 0,       // autodetected
            height: 0,
        },

        timegraph_center_x: 150,
        timegraph_center_y: 100,
        timegraph_height: 60,
        timegraph_width: 360,

        circle_radius: 60,
        circle_center_x: 80,
        circle_center_y: 100,

        axis_margin_left: 15,
        axis_margin_top: 15,
        axis_margin_bottom: 15,
        axis_margin_right: 25,

        refresh: 50,    // interval refresh in ms
        steps: 60,      // # of intervals to divide wave into

        cyclegraph_dot: {
            strokeStyle: "#ccc",
            lineWidth: 1.5,
            radius: 3.5,
            fillStyle: "Orange"
        },

        timegraph_dot: {
            strokeStyle: "#ccccff",
            lineWidth: 1.0,
            radius: 3.5,
            fillStyle: "Orange"
        },

        axes: {
            strokeStyle: "#999",
            lineWidth: 0.5
        },

        wave: {
            fillStyle: "rgba(0,0,0,0)",
            strokeStyle: "#C2A7DD",
            lineWidth: 1.5
        },

        combined: {
            color: "#4A93FA"
        },

        interval: {
            dotcolor: "rgba(255, 165, 0, 0.8)",
            lineStyle: "rgba(255, 165, 0, 0.5)",
            lineWidth: 1.0,
        },

        unitcircle: {
            fillStyle: "rgba(0,0,0,0)",
            strokeStyle: "#999",
            lineWidth: 0.5
        },

        cycle: {
            poscolor: "#37B610",
            negcolor: "#E95C59",
            zerocolor: "#999"
        },

        text: {
            font: "normal 12px Courier",
            fillStyle: "#888"
        }
    };

    function getURLParameter(name) {
        return decodeURIComponent((new RegExp('[?|&]' + name + '=' + '([^&;]+?)(&|#|;|$)').exec(location.search)||[,""])[1].replace(/\+/g, '%20'))||null;
    }

    function roundTo(n, digits) {
        return Math.round(n * Math.pow(10, digits)) / Math.pow(10, digits);
    }

    // TODO: perhaps unnecessary, but good for learning: load canvas caches (if possible) and draw into the original
    var cachedCanvas = {};
    function getCachedCanvas(key) {
        return cachedCanvas[key] || null;
    }

    function setCachedCanvas(key, ctx) {

        // flush cache
        if (_(cachedCanvas).keys().length > 25) {
            cachedCanvas = {};
        }

        cachedCanvas[key] = ctx;
    }

    function generateCanvas() {
        var canvas = document.createElement('canvas');
        canvas.width = settings.canvas.width;
        canvas.height = settings.canvas.height;
        return canvas;
    }

    function cachedRender(key, ctx, renderfn) {
        key = JSON.stringify(key);
        var cachedCanvas = getCachedCanvas(key);

        if (!cachedCanvas) {
            cachedCanvas = generateCanvas();
            cached_ctx = cachedCanvas.getContext("2d");
            resetCanvas(cached_ctx);
            renderfn(cached_ctx);
            setCachedCanvas(key, cachedCanvas);
        }

        ctx.drawImage(cachedCanvas, 0, 0);
    }

    // cycleFn: function(x) that returns value for a time point
    // key: unique key for this call, used for caching
    function timegraph_path(ctx, cycleFn, strokeStyle)
    {
        var N = settings.timegraph_width; // buttery-smooth, pixel-by-pixel
        var dx = 2 * (Math.PI) / N;;
        var x = 0;
        var px = settings.timegraph_center_x;
        var px_orig = px;
        var py = settings.timegraph_center_y;

        ctx.beginPath();
        ctx.lineWidth = settings.wave.lineWidth;
        ctx.strokeStyle = strokeStyle || settings.wave.strokeStyle;

        // have one extra point so curves wrap nicely
        for (var i = 0; i <= N; i++) {
            var x = 2 * Math.PI * i/N;
            y = cycleFn(x);

            var px = settings.timegraph_center_x + x * (180 / Math.PI) * settings.timegraph_width / 360;
            var py = settings.timegraph_center_y - settings.timegraph_height*y;

            if (i == 0) {
                ctx.moveTo(px, py);
            }
            else {
                ctx.lineTo(px, py);
            }
        }

        ctx.stroke();
        ctx.closePath();
    }

    function path_circ(ctx, x, y, r)
    {
        ctx.beginPath();
        ctx.arc(x, y, r, 0, Math.PI * 2, true);     //arc(x, y, radius, startAngle, endAngle, anticlockwise)
        ctx.stroke(); 
        ctx.closePath();
    }

    function path_line(ctx, x0, y0, x1, y1)
    {
        ctx.beginPath();
        ctx.moveTo(x0, y0);
        ctx.lineTo(x1, y1);
        ctx.stroke(); 
        ctx.closePath();
    }

    // place circle on canvas 
    function path_dot(ctx, x, y, radius)
    {
        radius = radius || 3.5;
        ctx.beginPath();
        ctx.arc(x, y, radius, 0, Math.PI * 2, true); // arc(x, y, radius, startAngle, endAngle, anticlockwise)
        ctx.fill(); 
        ctx.closePath();
    }

    // dot on cycle chart
    function cyclegraph_dot(ctx, x, y, fillStyle)
    {
        var x = settings.circle_center_x + settings.circle_radius*x;
        var y = settings.circle_center_y - settings.circle_radius*y;

        ctx.strokeStyle = settings.cyclegraph_dot.strokeStyle;
        ctx.lineWidth = settings.cyclegraph_dot.lineWidth;

        // line to origin
        path_line(ctx, settings.circle_center_x, settings.circle_center_y, x, y);

        // draw circle itself
        ctx.fillStyle = fillStyle || settings.cyclegraph_dot.fillStyle;
        path_dot(ctx, x, y, settings.cyclegraph_dot.radius);
    }

    // draw interval marker
    function cyclegraph_interval(ctx, x, y, color)
    {
        var x = settings.circle_center_x + settings.circle_radius*x;
        var y = settings.circle_center_y - settings.circle_radius*y;

        ctx.strokeStyle = color || settings.interval.fillStyle;
        ctx.lineWidth = settings.interval.lineWidth;

        // line to origin
        path_line(ctx, settings.circle_center_x, settings.circle_center_y, x, y);
    }

    function timegraph_interval(ctx, t, color)
    {
        var x = settings.timegraph_center_x + t * 180/Math.PI * settings.timegraph_width / 360;
        var min_y = settings.axis_margin_top;
        var max_y = settings.canvas.height - settings.axis_margin_bottom;

        ctx.fillStyle = color || settings.interval.fillStyle;
        ctx.lineWidth = settings.interval.lineWidth;

        // drop line to baseline
        path_line(ctx, x, min_y, x, max_y);
    }

    // dot on time chart
    function timegraph_dot(ctx, t, height, fillStyle)
    {
    	var x = settings.timegraph_center_x + t * 180/Math.PI * settings.timegraph_width / 360;
    	var y = settings.timegraph_center_y - settings.timegraph_height * height;

        ctx.fillStyle = fillStyle || settings.timegraph_dot.fillStyle;
        path_dot(ctx, x, y, settings.timegraph_dot.radius);

        ctx.strokeStyle = settings.timegraph_dot.strokeStyle;
        ctx.lineWidth = settings.timegraph_dot.lineWidth;
    }

    // parse cycle text and return array of cycle objects (amp, freq, phase)
    // cycles are 0th 1st 2nd 3rd... or 0th 1st & -1st 2nd & -2nd
    function getCycles() {
		$cycleInput = $('input[name=data-cycles]');
		var text = $cycleInput.val();

		text = text.replace(/\s+&\s+/g, '&');
	    var strings = _(text.split(/[\s,]+/)).reject(function(i){ return i == null || i == "";});

	    var index = 0;
	    var cycles = [];

	    function parseCycle(str, freq){
    		var matches = str.split(/[@:]/);
    		return {
    			freq: freq,
    			amp: parseFloat(matches[0]),
    			phase: parseFloat(matches[1] || 0)
    		};
	    }

    	_(strings).each(function(i){
    		var posneg = i.split('&');
    		cycles.push(parseCycle(posneg[0], index));

    		// specified negative cycle too
    		if (posneg[1]) {
    			cycles.push(parseCycle(posneg[1], -1 * index));
    		}

   			index++;
    	});

        cycles = _(cycles).reject(function(i){ return _.isNaN(i.amp); });

    	return cycles;
    }

    function resetCanvas(ctx) {
        ctx.clearRect(0, 0, settings.canvas.width, settings.canvas.height);
    }

    function drawAxes(ctx, scale) {

        // style axes
        ctx.strokeStyle = settings.axes.strokeStyle;
        ctx.lineWidth = settings.axes.lineWidth;

        // x-axis both graphs
        path_line(ctx,
            settings.circle_center_x - settings.circle_radius - settings.axis_margin_left,
            settings.circle_center_y,
            settings.timegraph_center_x + settings.timegraph_width,
            settings.timegraph_center_y);

        // y-axis for circle
        path_line(ctx, settings.circle_center_x, settings.axis_margin_top, settings.circle_center_x,settings.canvas.height - settings.axis_margin_bottom);

        // y-axis for time series
        path_line(ctx, settings.timegraph_center_x, settings.axis_margin_top, settings.timegraph_center_x, settings.canvas.height - settings.axis_margin_bottom);

        // unit circle
        ctx.fillStyle = settings.unitcircle.fillStyle;
        ctx.strokeStyle = settings.unitcircle.strokeStyle;
        ctx.lineWidth = settings.unitcircle.lineWidth;
        path_circ(ctx, settings.circle_center_x, settings.circle_center_y, settings.circle_radius * scale);

        // line for the wave itself
        ctx.fillStyle = settings.wave.fillStyle;
        ctx.strokeStyle = settings.wave.strokeStyle;
        ctx.lineWidth = settings.wave.lineWidth;
    }

    function drawFourier(ctx, options)
    {
        var start = new Date();

        options = options || {};

        // position to move to has been scaled along a circle
        var r = (step/settings.steps) * 2.0 * Math.PI;

        var cycles = getCycles();
        var N = cycles.length;
        var timeseries = Fourier.InverseTransform(cycles);
        var combined = Fourier.totalValue(r, cycles);
        var max_amplitude_time = _(_(timeseries).pluck('amp')).max();
        var max_real = _(_(timeseries).pluck('real')).max();

        // adjust scale if we are hiding the total
        if (!$('#showcombined').is(':checked')) {
            max_amplitude = _(_(cycles).pluck('amp')).max();
        }

        var scale = max_amplitude_time > 0 ? 1 / max_amplitude_time : 1;
        if (scale > 1) {
            scale = 1;
        }

        resetCanvas(ctx);
        drawAxes(ctx, scale);

        function getCycleColor(cycle) {
            var color = cycle.freq > 0 ? settings.cycle.poscolor : settings.cycle.negcolor;
            if (cycle.freq == 0){
                color = settings.cycle.zerocolor;
            }
            return color;
        }

        function drawStatus(text, color) {
            ctx.font = settings.text.font;
            ctx.fillStyle = color || settings.text.fillStyle;
            ctx.fillText(text, settings.timegraph_center_x + 10, canvas.height - settings.axis_margin_bottom);
        }

        function drawIntervals(){
            // draw lines showing the intervals
            _(timeseries).each(function(point){

                // ignore first interval, there's already an x-axis
                if (point.x > 0) {
                    timegraph_interval(ctx, point.x, settings.interval.lineStyle);
                }

                var value = Fourier.totalValue(point.x, {freq: 1, phase: 0, amp: 1});
                cyclegraph_interval(ctx, value.real * scale, value.im * scale, settings.interval.lineStyle);
            });
        }

        if (!$('input[name=data-time]').is(':focus')) {
            var str = _(timeseries).map(function(point){return Math.round(point.real * 10) / 10;}).join(" ");
            $('input[name=data-time]').val(str);
        }

        if ($('#showreverse').is(':checked')) {

            // show only the total and the cycle we want
            var soloFreq = parseInt($('#reversefreq').val());
            scale = 1 / max_amplitude_time;

            var cycle = {
                freq: -1 * soloFreq,
                phase: 0,
                amp: 1
            };

            var color = getCycleColor(cycle);
            var cycleTotal = Fourier.totalValue(r, cycle);

            drawIntervals();

            cyclegraph_dot(ctx, cycleTotal.real * scale, cycleTotal.im * scale, color);
            timegraph_dot(ctx, r, cycleTotal.real * scale, color);

            cachedRender(["timegraph_path", cycle, scale, color], ctx, function(ctx){
                timegraph_path(ctx, function(x){ return Fourier.totalValue(x, cycle).real * scale;}, color);
            });

            var totalReal = 0;
            var totalIm = 0;

            _(timeseries).each(function(point, i){

                // draw the multiplied signal
                var thisCycle = Fourier.totalValue(point.x, cycle);

                if (point.x < r) {
                    timegraph_dot(ctx, point.x, point.real * thisCycle.real * scale, settings.cycle.negcolor);
                    cyclegraph_dot(ctx, point.real * thisCycle.real * scale, point.real * thisCycle.im * scale, settings.cycle.negcolor);
                }

                totalReal += point.real * thisCycle.real;
                totalIm += point.real * thisCycle.im;
            });

            if (timeseries.length > 0 && r > timeseries[timeseries.length - 1].x) {
                // show the final average
                var avgReal = totalReal / N;
                var avgIm = totalIm / N;
                var avgRealRounded = roundTo(avgReal, 2);
                var avgImRounded = roundTo(avgIm, 2);
                var ampRounded = Math.round(Math.sqrt(avgReal * avgReal + avgIm * avgIm), 2);
                var phase = roundTo(Math.atan2(avgImRounded, avgRealRounded) * 180/Math.PI, 0);

                cyclegraph_dot(ctx, avgReal * scale, avgIm * scale, settings.combined.color);

                var text = "avg: " + " re: " + avgRealRounded + " im: " + avgImRounded;
                // text += " [" + ampRounded + (ampRounded != 0 && phase != 0 ? phase : '' ) +  "]";
                drawStatus(text, settings.combined.color);
            }

            return;
        }

        if ($('#showparts').is(':checked')) {
            _(cycles).each(function(cycle){
                var color = getCycleColor(cycle);
                var cycleTotal = Fourier.totalValue(r, cycle);

                cyclegraph_dot(ctx, cycleTotal.real * scale, cycleTotal.im * scale, color);
                timegraph_dot(ctx, r, cycleTotal.real * scale, color);
                timegraph_path(ctx, function(x){ return Fourier.totalValue(x, cycle).real * scale;}, color, cycle);
            });
        }

        // ticks
        if ($('#showdiscrete').is(':checked')){
            drawIntervals();
        }

        // current combined point
        if ($('#showcombined').is(':checked')) {
            cyclegraph_dot(ctx, combined.real * scale, combined.im * scale, settings.combined.color);
            timegraph_path(ctx, function(x){return Fourier.totalValue(x, cycles).real * scale;}, settings.combined.color, cycles);
            timegraph_dot(ctx, r, combined.real * scale, settings.combined.color);

            _(timeseries).each(function(point){
                timegraph_dot(ctx, point.x, point.real * scale, settings.interval.dotcolor);
                cyclegraph_dot(ctx, point.real * scale, point.im * scale, settings.interval.dotcolor);
            });
        }

        // label values
        if ($('#running').is(':checked') == false) {
            var text = "t: " + roundTo(r, 1) + " re: " + roundTo(combined.real, 1) + " im: " + roundTo(combined.im, 1);
        }
    }

    function init()
    {
        var canvas = $('#canvas').get(0);
        var ctx = canvas.getContext("2d");

        settings.canvas.width = canvas.width;
        settings.canvas.height = canvas.height;

        setInterval(function () {
            if ($('#running').is(':checked')) {
                drawFourier(ctx);
                advanceTime();
            }
        }, settings.refresh);

        step = 0;
        function advanceTime(){
            step++;

            if (step > settings.steps){
                step=0;
            }
        }

        $('#reset').click(function(e){
            e.preventDefault();
            step = 0;
            drawFourier(ctx);
        });

        $('input').not('.nochange').change(function(){drawFourier(ctx);}).keyup(function(){drawFourier(ctx);});

        $('#time').css('visibility', 'hidden').change(function(){
            step = ($(this).val() / 100) * settings.steps;
            drawFourier(ctx);
        });

        $('#running').change(function(){
            $('#time').css('visibility', $(this).is(':checked') ? 'hidden' : '');
            $('#time').val((step / settings.steps) * 100);
        });

        if (!Modernizr.inputtypes.range){
            $('#time').css('width', '30px');
        }

        $('input[name=data-time]').keyup(function(){
            var timeseries = Fourier.parseTimeSeries($(this).val());
            var transform = Fourier.Transform(timeseries);
            var newCycles = Fourier.getCyclesFromData(transform);
            var newString = Fourier.getStringFromCycles(newCycles);
            $('input[name=data-cycles]').val(newString);
            drawFourier(ctx, {dataupdate: false});
        });

        $('#canvas').click(function(){
            $('#running').click();
        })

        $('.mrfourier').click(function(e){
            e.preventDefault();
            $('.fourierchart').toggleClass('theme-dark');
        });
    };

    $(function(){
        init();

        var cycles = getURLParameter("cycles");
        if (cycles) {
            cycles = cycles.replace(/,/g, " ");
            $('input[name=data-cycles]').val(cycles);
        } else {
            var time = getURLParameter("time");
            if (time) {
                time = time.replace(/,/g, " ");
                $('input[name=data-cycles]').val("");
                $('input[name=data-time]').val(time).trigger('keyup');
                $('#running').trigger('click');
            }
        }
    });


var Fourier = {};

/*
    Transform a discrete time series to frequency components
    @param data (array): time-series numbers
    @returns frequencies: array of frequency objects, indexed by frequency (f=0 ... N-1):
        {real part, imaginary part, magnitude (computed), phase in degrees (computed) }
*/

Fourier.Transform = function(data) {
    var N = data.length;
    var frequencies = [];

    // for every frequency...
    for (var freq = 0; freq < N; freq++) {     
        var re = 0;
        var im = 0;

        // for every point in time...
        for (var t = 0; t < N; t++) {

            // Spin the signal _backwards_ at each frequency (as radians/s, not Hertz)
            var rate = -1 * (2 * Math.PI) * freq;

            // How far around the circle have we gone at time=t?
            var time = t / N;
            var distance = rate * time;

            // datapoint * e^(-i*2*pi*f) is complex, store each part
            var re_part = data[t] * Math.cos(distance);
            var im_part = data[t] * Math.sin(distance);

            // add this data point's contribution
            re += re_part;
            im += im_part;
        }

        // Close to zero? You're zero.
        if (Math.abs(re) < 1e-10) { re = 0; }
        if (Math.abs(im) < 1e-10) { im = 0; }

        // Average contribution at this frequency
        re = re / N;
        im = im / N;

        frequencies[freq] = {
            re: re,
            im: im,
            freq: freq,
            amp: Math.sqrt(re*re + im*im),
            phase: Math.atan2(im, re) * 180 / Math.PI     // in degrees
        };
    }

    return frequencies;
}

// return data point for all cycles {x, real, im, amp}
Fourier.totalValue = function(x, cycles) {
    cycles = _.isArray(cycles) ? cycles : [cycles];

    var real = 0;
    var im = 0;

    _(cycles).each(function(cycle){
        real += cycle.amp * Math.cos(x * cycle.freq + cycle.phase * Math.PI/180);
        im += cycle.amp * Math.sin(x * cycle.freq + cycle.phase * Math.PI/180);
    });

    return {
        x: x,
        real: real,
        im: im,
        amp: Math.sqrt(real*real + im*im)
    };
};

Fourier.realValue = function(x, cycle) {
    return Fourier.totalValue(x, cycle).real;
};

Fourier.imaginaryValue = function(x, cycle) {
    return Fourier.totalValue(x, cycle).im;
};

// return time series of data points {x, real, im, amp}
Fourier.InverseTransform = function(cycles) {

    var timeseries = [];
    var len = cycles.length;
    for (var i = 0; i < len; i++) {
        var pos = i/len * 2 * Math.PI;
        var total = Fourier.totalValue(pos, cycles);
        timeseries.push(total);
    }
    return timeseries;
};

// Do a fourier transform on this data string
Fourier.getCyclesFromData = function(data, rounding){
    rounding = rounding || 2;
    return _(data).map(function(i){
        return {
            freq: i.freq,
            phase: Math.round(i.phase * Math.pow(10, 1)) / Math.pow(10, 1),
            amp: Math.round(i.amp * Math.pow(10, rounding)) / Math.pow(10, rounding)
        };
    });
};

// convert cycles into parseable string
Fourier.getStringFromCycles = function(cycles){
    var str = "";
    _(cycles).each(function(i){
        str += i.amp;
        if (i.phase != 0 && i.amp != 0){
            str += ":" + i.phase;
        }
        str += " ";
    });
    return str;
}

// Return array of numbers given a time-series string ("1 2.3 -4"). Comma or space separated
Fourier.parseTimeSeries = function(text) {
    var strings = _(text.split(/[\s,]+/)).reject(function(i){ return i == null || i == "";});
    return _(strings).map(function(i){return parseFloat(i);});
}
</script>
<style type="text/css">
input[type=text] {
	font-family: monospace;
    width: 240px;
}

input[name=data-time] {
    width: 160px;
}

#time {
    width: 100px;
}

.fourierchart {
    width: 520px;
    border: 1px solid #ccc;
    position: relative;
}

label[for=showcombined] {
    color: #4A93FA;
}

label[for=showparts] {
    color: #37B610;
}

label[for=showdiscrete]{
    color: #E7A020;
}

.commands {
    font-family: 'Lucida Console', 'Courier New', monospace;
    font-size: 11px;
    background: #eaeaea;
    color: #333;
    padding: 1px 4px;
}

.gradient {
    background: rgb(238,238,238); /* Old browsers */
    background: -moz-linear-gradient(top,  rgba(238,238,238,1) 0%, rgba(204,204,204,1) 100%); /* FF3.6+ */
    background: -webkit-gradient(linear, left top, left bottom, color-stop(0%,rgba(238,238,238,1)), color-stop(100%,rgba(204,204,204,1))); /* Chrome,Safari4+ */
    background: -webkit-linear-gradient(top,  rgba(238,238,238,1) 0%,rgba(204,204,204,1) 100%); /* Chrome10+,Safari5.1+ */
    background: -o-linear-gradient(top,  rgba(238,238,238,1) 0%,rgba(204,204,204,1) 100%); /* Opera 11.10+ */
    background: -ms-linear-gradient(top,  rgba(238,238,238,1) 0%,rgba(204,204,204,1) 100%); /* IE10+ */
    background: linear-gradient(to bottom,  rgba(238,238,238,1) 0%,rgba(204,204,204,1) 100%); /* W3C */
    filter: progid:DXImageTransform.Microsoft.gradient( startColorstr='#eeeeee', endColorstr='#cccccc',GradientType=0 ); /* IE6-9 */
}

.theme-dark #canvas {
    background: #0F2338;
}

.help {
    font-family: Verdana;
    font-size: 12px;
    position: absolute;
    background: #fafafa;
    padding: 5px;
    color: #333;
    border: 1px solid #ccc;
    top: -150px;
    left: -420px;
    display: none;
    width: 400px;
}

.mrfourier:hover .help {
    xdisplay: block;
}

.mrfourier {
    float: right;
    display: inline-block;
    width: 40px;
    height: 40px;
    /* image from Wikipedia: http://upload.wikimedia.org/wikipedia/commons/thumb/a/aa/Joseph_Fourier.jpg/490px-Joseph_Fourier.jpg; */
    background: url(https://betterexplained.com/examples/fourier/fourier.png);
    position: relative;
    top: -24px;
    left: -3px;
    background-size: 100%;
    background-repeat-x: no-repeat;
    background-position-x: 0px;
    border-radius: 81px;
    border: 1px solid #E5E5E5;
}
</style></head>

<body data-new-gr-c-s-check-loaded="14.994.0" data-gr-ext-installed=""><div id="StayFocusd-infobar" style="display:none;">
    <img src="chrome-extension://laankejkbhbdhmipfmgcngdelahlfoji/common/img/eye_19x19_red.png">
    <span id="StayFocusd-infobar-msg"></span>
    <span id="StayFocusd-infobar-links">
        <a id="StayFocusd-infobar-never-show">hide forever</a>&nbsp;&nbsp;|&nbsp;&nbsp;
        <a id="StayFocusd-infobar-hide">hide once</a>
    </span>
</div>
<div class="fourierchart">
<div class="commands gradient">
Cycles <input type="text" name="data-cycles" value="1 0 0">
Time <input type="text" name="data-time" class="nochange">
</div>
<canvas id="canvas" width="520px" height="200px" style="">
This browser doesn't support canvas! Try <a href="https://google.com/chrome">Google Chrome</a>.
</canvas>
<div class="commands gradient2">
<input type="checkbox" id="showcombined" checked="checked" name="showcombined"><label for="showcombined">Total</label>
<input type="checkbox" id="showparts" checked="checked" name="showparts"><label for="showparts">Parts</label>
<input type="checkbox" id="showdiscrete" name="showdiscrete"><label for="showdiscrete">Ticks</label>
<input type="checkbox" id="showreverse" name="showreverse"><label for="showreverse">Derive</label>
<input type="text" id="reversefreq" style="width: 20px" value="1">

<input type="checkbox" id="running" checked="checked" name="running" class="nochange"><label for="running">Running?</label>
<input type="range" id="time" min="0" max="100" style="visibility: hidden;">
<a href="https://betterexplained.com/examples/fourier/?cycles=0,2" class="mrfourier">
<div class="help">
Enter frequencies (cycles/sec aka Hz) and see their time values, or vice-versa
<ul>
<li>Frequency input: <code>1 0 2:45</code> is 0Hz (size 1) + 1Hz (size 0) + 2Hz (size 2, phase-shifted 45-degrees) </li>
<li>Time input: <code>1 2 3</code> generates a wave that hits 1 2 3
</li></ul>
Click Mr. Fourier for night mode. Have fun!
<br>
</div>
</a>
</div>
</div>
<script defer="" src="./betterexplained.com_files/beacon.min.js.下载" data-cf-beacon="{&quot;r&quot;:1,&quot;si&quot;:10,&quot;rayId&quot;:&quot;61f5a3df9c88fe58&quot;,&quot;version&quot;:&quot;2021.2.0&quot;}"></script>


</body></html>